package org.zn.note.jdk;

import java.util.ServiceLoader;

/**
 * SPI(Service Provider Interface)，服务提供者接口
 * [模块交互面向接口] 系统设计的各个抽象，往往有很多不同的实现方案，在面向的对象的设计里，一般推荐模块之间基于接口编程，模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类，就违反了可拔插的原则，如果需要替换一种实现，就需要修改代码。
 * 为了实现在模块装配(对接)的时候能在程序里动态指明，这就需要一种服务发现机制。
 * Java SPI 就是提供这种机制：为某个接口寻找服务实现的机制。有点类似IOC的思想，就是将装配的控制权移到程序之外，在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。
 * <p>
 * 常见场景：
 * 1、数据库驱动加载接口实现类的加载：JDBC加载不同类型数据库的驱动
 * 2、日志门面接口实现类加载：SLF4J加载不同提供商的日志实现类
 * 3、Spring中大量使用了SPI，比如：对servlet3.0规范ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
 * 4、Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装，允许用户扩展实现Filter接口
 * <p>
 * 遵循约定：
 * 1、当服务提供者提供了接口的一种具体实现后，在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件，内容为实现类的全限定名；
 * 2、接口实现类所在的jar包放在主程序的classpath中;
 * 3、主程序通过java.util.ServiceLoder动态装载实现模块，它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名，把类加载到JVM；
 * 4、SPI的实现类必须携带一个不带参数的构造方法；
 * <p>
 * 实现步骤：
 * 1、Step 1：创建services文件（目录：META-INF/services/   文件名：接口全限定名）
 * 2、Step 2：文件里加上实现类的全限定名 （实现必须是public的，要不它没权实例化）。多个实现类，一行一个。
 * 3、ServiceLoader<接口> serviceImpls = ServiceLoader.load(接口.class);  返回所有配置了的实现类
 * <p>
 * 实现原理：{@link ServiceLoader}
 * 其实就是一个工具类，读配置，创建实例。
 * ServiceLoader.load的时候并没有实例化实现，而是等到用到的时候再实例化的
 * 1、ClassLoader cl = Thread.currentThread().getContextClassLoader()
 * 2、providers = LinkedHashMap<>()，清空providers
 * 3、LazyIterator 遍历时用的这个，懒加载
 * <p>
 * 优点：
 * 1、可以跨Jar包找services，源框架不用：
 * 硬编码import导入实现类、不用全路径反射如Class.forName("com.mysql.jdbc.Driver")
 * 服务提供模块将服务注册到指定位置(services文件)，源框架直接取
 * 缺点：
 * 1、只能通过遍历来获取实现类，不能通过参数来指定实现类
 * 2、遍历后，没有用的实现也实例化了
 * 3、多线程不安全
 */
public class spi {

    // 接口
    interface Service {
        Object doService();
    }

    // 接口实现A
    public static class AServiceImpl implements Service {
        @Override
        public Object doService() {
            return "This is AServiceImpl";
        }
    }

    // 接口实现B
    public static class BServiceImpl implements Service {
        @Override
        public Object doService() {
            return "This is BServiceImpl";
        }
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        // 内部接口的全限定名：org.zn.note.jdk.A_Java.util.spi$Service
        System.out.println(Service.class.getName());
        // 内部静态类的全限定名：org.zn.note.jdk.A_Java.util.spi$AServiceImpl
        System.out.println(AServiceImpl.class.getName());
        // Step 1：创建services文件（目录：META-INF/services/   文件名：接口全限定名）
        // META-INF/services/org.zn.note.jdk.A_Java.util.spi$Service
        // Step 2：文件里加上实现类的全限定名 （实现必须是public的，要不它没权实例化）
        // org.zn.note.jdk.A_Java.util.spi$AServiceImpl
        // org.zn.note.jdk.A_Java.util.spi$BServiceImpl
        // Step 3：ServiceLoader通过配置文件加载所有实现
        ServiceLoader<Service> services = ServiceLoader.load(Service.class);
        for (Service service : services) {
            System.out.println(service.doService());
        }
    }
}